package com.stropheware.droidDefense;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

import com.stropheware.pathfinding.Path;
import com.stropheware.pathfinding.PathFinder;
import com.stropheware.pathfinding.UnionFind;
import com.stropheware.util.MethodTimer;

public class GameThread extends Thread{
	
	private static final String TAG = "GameThread";
	
	private SurfaceHolder surfaceHolder;
	private GameBoard gameBoard;
	private Menu menu;
	private PlayerMenu playerMenu;
	private Context context;

	private Drawable tempTowerDrawable;
	private boolean drawingTempBasicTowerDrawable;
	
	int boundsX;
	int boundsY;
	
	/** Used for pop ups for enemy/tower*/
	private AbstractTower activeTower = new BasicTower();
	private AbstractEnemy activeEnemy;
	
	private GameManager gameManager;
	
	private UnionFind unionFind;

	private static final MethodTimer drawTimer = new MethodTimer();
	
	public static final int BASIC_TOWER = 1;
	public static final int AOE_TOWER = 2;
	public static final int UTILITY_TOWER = 3;
	
	public static int towerType;
	
	/** Indicate whether the surface has been created & is ready to draw */
    private boolean runThread = false;
    
    /**Temporary paint for drawing Path*/
    private static Paint pathPaint = new Paint();
    static{
    	pathPaint.setColor(Color.GREEN);
    }
    
    /**One instance drawables*/
    private final Rect pathRect = new Rect();
    
	public GameThread(SurfaceHolder surfaceHolder, int width, int height, Context context) {
		this.context = context;
		this.surfaceHolder = surfaceHolder;
		gameBoard = new GameBoard(width, height, context);
		unionFind = new UnionFind(gameBoard);
		menu = new Menu(gameBoard, context);
		gameManager = new GameManager(gameBoard, this, context);
		playerMenu = new PlayerMenu(gameBoard, gameManager, context);
		activeEnemy = new BalancedEnemy(context, gameBoard, this.gameManager.enemyManager, 0 );
		updateCurrentPath();
	}
	
	private void resetDrawingBooleans() {
		drawingTempBasicTowerDrawable = false;
	}
	
	public void acceleratorShaken() {
		Log.d(TAG, "accel shaken");
		gameManager.accelShaken();
	}

	public boolean doOnKeyDown(int keyCode, KeyEvent event){
		Log.d(TAG, "Key pressed.");
		gameManager.accelShaken();
		return true;
	}
	
	public void doDraw(Canvas canvas) {
		drawTimer.start();
		canvas.drawColor(Color.WHITE);
		drawPath(canvas);
		gameBoard.drawGameBoard(canvas);
		drawTowerRadius(canvas);
		menu.drawMenu(canvas);
		playerMenu.drawMenu(canvas);
		gameManager.drawGame(canvas);
		drawTemporaryDrawables(canvas);
		drawTowerPopUp(canvas);
		drawEnemyPopUp(canvas);
		drawTimer.end();
//		Log.d(TAG,"Draw time: " + drawTimer.getTimeInMilliseconds());
	}
	
	private void drawTemporaryDrawables(Canvas canvas) {
		if(drawingTempBasicTowerDrawable)
			RendererUtil.drawDragTower(tempTowerDrawable,canvas, gameBoard);
	}
	
	
	/*
	 * Called whenever screen is touched
	 * Must be efficient
	 */
	public void onTouch(MotionEvent event) {
		int x = (int) event.getX();
		int y = (int) event.getY();
		
		if(event.getAction() == MotionEvent.ACTION_DOWN) {
			if(activeTower.display){
				if(isTouchEventOnUpgrade(activeTower, x, y)){
					activeTower.upgrade();
				}
				else if(isTouchEventOnSell(activeTower, x, y)){
					unionFind.removeTower(activeTower);
					gameManager.sellTower(activeTower);
				}
				activeTower.display = false;
			}
			
			else if	(activeEnemy.displayPopUp)activeEnemy.displayPopUp = false;
			//Log.d(TAG, "Screen touched down: (x,y) " + event.getX() + "," + event.getY());
			else if(isTouchEventInMenu(x)) {
				//Log.d(TAG, "Down-Clicked in menu");
				if(menu.touchFirstMenuTower(x, y)) {
					//Log.d(TAG, "Down-Clicked in first MenuTower");
					setTempTower(BASIC_TOWER, x, y);
				}else if (menu.touchSecondMenuTower(x, y)) {
					//Log.d(TAG, "Down-Clicked in second MenuTower");
					setTempTower(AOE_TOWER, x, y);
				}else if (menu.touchThirdMenuTower(x, y)) {
					//Log.d(TAG, "Down-Clicked in third MenuTower");
					setTempTower(UTILITY_TOWER, x, y);
				}
			}
			else {
				//Log.d(TAG, "Down-Clicked on gameboard or nothing");
				Cell tempCell = gameBoard.getNearestCell(x, y);
				if(tempCell == null)
					return;
				AbstractTower tempTower = tempCell.occupiedTower;
				if(isTower(tempTower)){
					activeTower = tempTower;
					activeTower.showPopUp();
				}
				else if(clickedOnEnemy(x, y)) {
					activeEnemy.displayPopUp = true;
				}
			}
		}
		else if(event.getAction() == MotionEvent.ACTION_MOVE) {
			//Log.d(TAG, "In movement: (x,y) " + event.getX() + "," + event.getY());
			if(drawingTempBasicTowerDrawable) {
				setTempDrawableBounds(tempTowerDrawable, x, y);
			}
		}
		else if(event.getAction() == MotionEvent.ACTION_UP) {
			//Log.d(TAG, "Screen touch released: (x,y) " + event.getX() + "," + event.getY());
			//Log.d(TAG, "Release-Clicked on gameboard");
			if(drawingTempBasicTowerDrawable) {
				//Log.d(TAG, "Placed basicTower on gameboard");
				Rect boundRect = tempTowerDrawable.getBounds();
				Cell tempCell = gameBoard.getCellNumIndex(boundRect.top, boundRect.left);
				if(isValidPlacement(boundRect.left,boundRect.top)) {
					if(!gameBoard.isOnCurrentPath(tempCell)){
						addTower(boundRect.top, boundRect.left, tempCell);
					}
					else {
						gameBoard.addTempTower(tempCell);
						MethodTimer mt = new MethodTimer();
						mt.start();
						updateCurrentPath();
						mt.end();
						Log.d(TAG, "pathfinding took: " + mt.getTimeInMilliseconds() + " milliseconds");
						if(gameManager.enemyManager.globalPath != null) {
							gameBoard.removeTempTower(tempCell);
							addTower(boundRect.top, boundRect.left, tempCell);
						}else {
							gameBoard.removeTempTower(tempCell);
							updateCurrentPath();
						}
					}
				}
			}
			resetDrawingBooleans();
		}
	}
	
	private void setTempTower(int type, int x, int y) {
		switch(type){
			case BASIC_TOWER:
				tempTowerDrawable = context.getResources().getDrawable(R.drawable.basic_tower);
				break;
			case AOE_TOWER:
				tempTowerDrawable = context.getResources().getDrawable(R.drawable.aoe_tower);
				break;
			case UTILITY_TOWER:
				tempTowerDrawable = context.getResources().getDrawable(R.drawable.utility_tower);
				break;
		}
		towerType = type;
		drawingTempBasicTowerDrawable = true;
		setTempDrawableBounds(tempTowerDrawable, x, y);
	}

	private void addTower(int top, int left, Cell tempCell){
		AbstractTower newTower = getTowerType(context, gameBoard, top, left, towerType);
		gameManager.towerManager.addTower(tempCell, newTower);
		unionFind.addTower(newTower);
	}
	
	private boolean isTower(AbstractTower tower){
		return tower != null;
	}
	
	private void setTempDrawableBounds(Drawable drawable, int x, int y) {
		boundsX = (x + gameBoard.startXPixel) - (x % gameBoard.cellSideLength);
		boundsY = (y + gameBoard.startYPixel) - (y % gameBoard.cellSideLength);
		drawable.setBounds(boundsX - 2 * gameBoard.towerWidthPx, boundsY - (gameBoard.towerWidthPx/2), boundsX -  gameBoard.towerWidthPx , boundsY + (gameBoard.towerWidthPx/2));
		if(!isValidPlacement(drawable.getBounds().left,drawable.getBounds().top))
			RendererUtil.dragPaint.setColor(0xaacc0000);
		else
			RendererUtil.dragPaint.setColor(0xaa00cc00);	
	}
	
	private boolean isTouchEventInMenu(int x) {
		return x >= (gameBoard.actualWidth - gameBoard.actualMenuWidth);
	}
	
	private boolean isValidPlacement(int x, int y){
		/* Check if actually in gameBoard */
		Cell topLeft, bottomRight, topRight, bottomLeft;
		try{
			//Log.d(TAG,"Xinc: " + x + " Xconv: "+ (x-gameBoard.startXPixel)/gameBoard.cellSideLength + " Yinc " + y + " Yconv: " + (y+gameBoard.startYPixel)/gameBoard.cellSideLength);	
			topLeft = gameBoard.cellGrid[(x-gameBoard.startXPixel)/gameBoard.cellSideLength][(y-gameBoard.startYPixel)/gameBoard.cellSideLength];
			bottomRight = gameBoard.cellGrid[(x+gameBoard.startXPixel+gameBoard.towerWidthPx-gameBoard.cellSideLength)/gameBoard.cellSideLength][(y+gameBoard.startYPixel+gameBoard.towerWidthPx-gameBoard.cellSideLength)/gameBoard.cellSideLength];
			topRight = gameBoard.cellGrid[(x+gameBoard.startXPixel+gameBoard.towerWidthPx-gameBoard.cellSideLength)/gameBoard.cellSideLength][(y-gameBoard.startYPixel)/gameBoard.cellSideLength];
			bottomLeft = gameBoard.cellGrid[(x-gameBoard.startXPixel)/gameBoard.cellSideLength][(y+gameBoard.startYPixel+gameBoard.towerWidthPx-gameBoard.cellSideLength)/gameBoard.cellSideLength];
		}
		catch(ArrayIndexOutOfBoundsException e){return false;}
		if(!gameManager.towerManager.isValidPlacement(topLeft, bottomRight, topRight, bottomLeft)) return false;
		if(topLeft.content == Cell.RESERVED || bottomRight.content == Cell.RESERVED ||
				topRight.content == Cell.RESERVED || bottomLeft.content == Cell.RESERVED) return false;
		if(!unionFind.isValidPlacement(topLeft)) return false;
		return true;
	}

	private boolean isTouchEventOnUpgrade(AbstractTower tower, int x, int y){
		return tower.upgrade.copyBounds().contains(x, y);
	}
	
	private boolean isTouchEventOnSell(AbstractTower tower, int x, int y){
		return tower.sell.copyBounds().contains(x, y);
	}
	
	private boolean clickedOnEnemy(float x, float y){
		int activeEnemyIndex = this.gameManager.enemyManager.activeEnemy;
		int breakCondition = this.gameManager.enemyManager.enemyArray[activeEnemyIndex].length;
		for(int i = 0; i < breakCondition; i++){
			if(gameManager.enemyManager.enemyArray[activeEnemyIndex][i].drawable.copyBounds().contains((int)x, (int)y)){
					activeEnemy = this.gameManager.enemyManager.enemyArray[activeEnemyIndex][i];
					if(activeEnemy.visible)
						return true;
			}
		}
		return false;
	}
	
	
	private void drawTowerRadius(Canvas canvas) {
		if(activeTower.display)
			RendererUtil.drawTowerRadius(activeTower,canvas);
	}
	
	private void drawTowerPopUp(Canvas canvas){
		if(activeTower.display)
			RendererUtil.drawTowerPopUp(activeTower, canvas, gameBoard);
	}
	
	private void drawEnemyPopUp(Canvas canvas){
		if(activeEnemy.displayPopUp){
			RendererUtil.drawEnemyPopUp(activeEnemy, canvas, gameBoard);
		}
	}
	
	private void drawPath(Canvas canvas){
		int left, top;
		Path currentPath = gameManager.enemyManager.globalPath;
		if(currentPath != null){
			for(int i = 0; i < currentPath.steps.size(); i++){
				try {
					left = gameBoard.nodes[currentPath.steps.get(i).x][currentPath.steps.get(i).y].occupiedCells[0].pxColumn;
					top = gameBoard.nodes[currentPath.steps.get(i).x][currentPath.steps.get(i).y].occupiedCells[0].pxRow;
					pathRect.set(left, top, left+gameBoard.cellSideLength+gameBoard.cellSideLength+gameBoard.cellSideLength, top+gameBoard.cellSideLength+gameBoard.cellSideLength+gameBoard.cellSideLength);
					canvas.drawRect(pathRect, pathPaint);
				}catch(ArrayIndexOutOfBoundsException e){Log.d(TAG,"Out of bounds array!");}
			}
		}
	}
	
	public void setRunning(Boolean runThread) {
		this.runThread = runThread;
	}
	
	public void run() {
		gameManager.startGame();
        Canvas canvas = null;
		while (runThread) {
			for(int i = 0; i < 15; i++)
			gameManager.updateGame();
	        try {
	            canvas = surfaceHolder.lockCanvas(null);
	            synchronized (surfaceHolder) {
	                doDraw(canvas);
	            }
	        } finally {
	            if (canvas != null) {
	                surfaceHolder.unlockCanvasAndPost(canvas);
	            }
	        }
		}
		Log.d(TAG, "thread exited");
    }
	
	private AbstractTower getTowerType(Context context, GameBoard gameBoard, int top, int left, int type){
		switch(type){
		case BASIC_TOWER: return new BasicTower(context, gameBoard, top, left);
		case AOE_TOWER: return new AoETower(context, gameBoard, top, left);	
		case UTILITY_TOWER: return new UtilityTower(context, gameBoard, top, left);
		default: return null;
		}
	}
	
	public void updateCurrentPath() {
		try {
			if(gameManager.roundInProgress) {
				ArrayList<AbstractEnemy> activeEnemies = gameManager.enemyManager.getActiveEnemies();
				ArrayList<Thread> threads = new ArrayList<Thread>();
				Log.d(TAG, "starting " + activeEnemies.size() + " pathfinders");
				for(int i = 0; i < activeEnemies.size(); i++) {
					AbstractEnemy enemy = activeEnemies.get(i);
					PathFinder pf = new PathFinder(gameManager.gameBoard, 500, false);
					pf.enemyToFindPath = enemy;
					if(i == 0) pf.targetNode = gameBoard.targetNode;
					else {
						AbstractEnemy previousEnemy = activeEnemies.get(i-1); //enemy before
						pf.targetNode = PathFinder.getNearestNode(previousEnemy.xPixel, previousEnemy.yPixel, gameBoard);
					}
					Thread pft = new Thread(pf);
					threads.add(pft);
					pft.start();
				}
				PathFinder p = new PathFinder(gameManager.gameBoard, 500, false);
				p.globalEnemyManager = gameManager.enemyManager;
				Thread pt = new Thread(p);
				threads.add(pt);
				pt.start();
				for(int j = 0; j < threads.size(); j++) {
					threads.get(j).join();
				}
				Log.d(TAG, "appending " + (activeEnemies.size() - 1) + " paths");
				for(int n = 1; n < activeEnemies.size(); n++) {
					activeEnemies.get(n).addStepsToPath(activeEnemies.get(n-1).currentPathSteps);
				}
			}
			else {
				PathFinder pf = new PathFinder(gameManager.gameBoard, 15000, false);
				pf.globalEnemyManager = gameManager.enemyManager;
				Thread pft = new Thread(pf);
				pft.start();
				//may want to do something in b/w here
				pft.join();
			}
			gameBoard.restCellPathBooleans();
			Cell [] temp;
			for(int i = 0; i < gameManager.enemyManager.globalPath.steps.size(); i++) {
				temp = gameBoard.nodes[gameManager.enemyManager.globalPath.steps.get(i).x][gameManager.enemyManager.globalPath.steps.get(i).y].occupiedCells;
				for(int x = 0; x < temp.length; x++) temp[x].isInPath = true;
			}
		} catch (InterruptedException e) {Log.d(TAG, "exception! =(");}
	}
}
